/* eslint-disable no-shadow */
/* eslint-disable no-param-reassign */
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
const { Controller } = require('egg');
const { isEqual, omit, forEach } = require('lodash');
const { archi, lock } = require('../../utils/archi');
const Util = require('../../utils/event');

class DefaultController extends Controller {
  async sendArchi() {
    const {
      ctx: { socket, args },
    } = this;
    const { query } = socket.handshake;

    const architecture =
      archi[query.id] ||
      JSON.parse(
        (await this.app.mysql.query(`select architecture from project where id=?`, [query.id]))[0]
          .architecture || '[]'
      );
    if (!archi[query.id]) {
      archi[query.id] = architecture;
    }

    const { cmds, revert } = args[0];

    const projectLock = lock[query.id];

    let isConflicting = false;
    const cmdsBack = [];
    cmds.forEach(cmd => {
      const { event, data, version } = cmd;
      if (!projectLock[data.id]) {
        projectLock[data.id] = {};
      }
      if ((Util.isAddEvent(event) && revert) || (Util.isRemoveEvent(event) && !revert)) {
        if (projectLock[data.id].deleted) {
          cmd.noRevert = true;
          isConflicting = true;
        } else {
          const judgeEqual = (version1, version2) => {
            for (const key in version1) {
              if (typeof version1[key] === 'number') {
                if (version1[key] < (version2[key] || 0)) {
                  cmd.conflict = [version, projectLock[data.id]];
                  isConflicting = true;
                  break;
                }
              } else if (typeof version1[key] === 'object') {
                judgeEqual(version1[key], version2[key] || {});
              }
            }
            for (const key in version2) {
              if (typeof version2[key] === 'number') {
                if ((version1[key] || 0) < version2[key]) {
                  cmd.conflict = [version, projectLock[data.id]];
                  isConflicting = true;
                  break;
                }
              } else if (typeof version1[key] === 'object') {
                judgeEqual(version1[key], version2[key] || {});
              }
            }
          };
          judgeEqual(version, projectLock[data.id]);
        }
      } else if (Util.isChangeEvent(event)) {
        const { key } = data;
        if (projectLock[data.id].deleted) {
          // 修改不生效，已经删除
          cmd.noRevert = true;
          isConflicting = true;
          return;
        }
        if (key === 'children') {
          const prev = (!revert ? data.prev[key] : data.next[key]) || [];
          const next = (revert ? data.prev[key] : data.next[key]) || [];

          const children = architecture.find(item => item.id === cmd.data.id)?.children || [];
          prev.forEach(item => {
            if (!next.find(item2 => item === item2)) {
              const index = children.findIndex(item3 => item3 === item);
              if (index > 0) {
                children.splice(index, 1);
              }
            }
          });
          next.forEach(item => {
            if (!prev.find(item2 => item === item2)) {
              children.push(item);
            }
          });
          if (revert) {
            data.prev[key] = children;
            if (!isEqual(data.prev[key], children)) {
              cmdsBack.push(cmd);
            }
          } else {
            data.next[key] = children;
            if (!isEqual(data.next[key], children)) {
              cmdsBack.push(cmd);
            }
          }
        } else if (key === 'ports') {
          if (!projectLock[data.id][key]) {
            projectLock[data.id][key] = {};
          }

          for (const portId in version) {
            if (version[portId] <= projectLock[data.id][key][portId] || 0) {
              isConflicting = true;
              break;
            }
          }
          if (!isConflicting) {
            const prev = (!revert ? data.prev[key]?.items : data.next[key]?.items) || [];
            const next = (revert ? data.prev[key]?.items : data.next[key]?.items) || [];
            const ports = architecture.find(item => item.id === cmd.data.id)?.ports?.items || [];
            forEach(prev, item => {
              // 删除
              if (!next.find(item2 => item.id === item2.id)) {
                const index = ports.findIndex(item3 => item3.id === item.id);
                if (index > 0) {
                  ports.splice(index, 1);
                }
              }
            });
            forEach(next, item => {
              const item2 = prev.find(item2 => item.id === item2.id);
              // 新增
              if (!item2) {
                ports.push(item);
                // 修改
              } else if (!isEqual(item, item2)) {
                const index = ports.findIndex(item2 => item.id === item2.id);
                ports.splice(index, 1, item);
              }
            });
            if (revert) {
              data.prev[key].items = ports;
              if (!isEqual(data.prev[key].items, ports)) {
                cmdsBack.push(cmd);
              }
            } else {
              data.next[key].items = ports;
              if (!isEqual(data.next[key].items, ports)) {
                cmdsBack.push(cmd);
              }
            }
          }
        } else if (version <= (projectLock[data.id][key] || 0)) {
          cmd.noRevert = true;
          isConflicting = true;
        }
      }
    });
    if (isConflicting) {
      socket.emit('conflictRevert', { cmds, revert: !revert });
      return;
    }
    if (cmdsBack.length > 0) {
      socket.emit('archi', { cmds: cmdsBack, revert });
    }

    cmds.forEach(cmd => {
      const { event, data } = cmd;
      const index = architecture.findIndex(item => item.id === cmd.data.id);
      const cell = architecture[index];

      if ((Util.isAddEvent(event) && revert) || (Util.isRemoveEvent(event) && !revert)) {
        // eslint-disable-next-line no-unused-expressions
        architecture.splice(index, 1);
        projectLock[data.id].deleted = true;
      } else if ((Util.isAddEvent(event) && !revert) || (Util.isRemoveEvent(event) && revert)) {
        if (data.node || data.edge) {
          architecture.push(data.props);
        }
        projectLock[data.id].deleted = false;
      } else if (Util.isChangeEvent(event)) {
        const { key } = data;
        if (key && cell) {
          const value = revert ? data.prev[key] : data.next[key];
          cell[key] = value;
          if (typeof cmd.version === 'number') {
            projectLock[data.id][data.key] = cmd.version;
          } else {
            projectLock[data.id][data.key] = {
              ...projectLock[data.id][data.key],
              ...cmd.version,
            };
          }
        }
      }
    });

    socket.to(query.id).emit('archi', args[0]);
  }
}

module.exports = DefaultController;
